iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 4
1

一路上感謝各位讀者們的支持和回饋。
本 30 天系列文目前已經將篇幅重新整理、編纂成冊。
《JavaScript 概念三明治》在天瓏書局上架囉!
喜歡這個系列,想閱讀更詳細原理說明的讀者可以參考:
https://www.tenlong.com.tw/products/9789864347575

今天我們要來談談「範疇( Scope )」。

Outline

  • 詞彙環境 ( Lexical Environment )
  • 什麼是範疇 ( Scope )
  • Function Scope
  • Block Scope
  • 總結

語彙環境 ( Lexical Environment )

在講到範疇以前,我必須先提一下一個很重要而且相關的概念,那就是語彙環境,弄清楚語彙環境之後,後續我們講到範疇就比較能夠區分差異性。語彙環境代表程式碼在程式中實際的物理位置。白話一點來說就是:

你把這段程式碼寫在哪 ?

既然是「詞彙」,就代表跟文法、語法相關,所以跟語法分析脫不了關係。語彙環境會影響 JavaScript 與法解析器分析程式碼的結果。回憶一下前幾天我們提到 JS 引擎的時候,JS 在被執行時,會先會被丟給語法分析器解析。

還記得抽象語法樹 ( AST ) 嗎?某段變數宣告寫在全域,或寫在函式宣告裡面,都會產生不ㄧ樣的抽象語法樹。語彙環境的差異就由這個階段產出的 AST 來決定。因此,程式碼的實際位置對我們後續在 JavaScript 其他觀念時非常重要。

什麼是範疇 ( Scope )

上一章節我們提到除了全域的執行環境 ( Global Ex. Context ) 之外,每當一個函式被呼叫的時候,一個對應的函式 ( Function Ex. Context )執行環境也會被產生,執行完之後就會離開。而在這個由函式裡面宣告的變數只能在離開該執行環境之前被取得、採用,而在全域執行環境下也無法直接取得在函式內宣告的變數。像這樣有限的存取範圍就是 「 範疇 」。用白話一點的方式說:

範疇是變數可以被使用的範圍

再讓我們來看個這段程式碼:

https://ithelp.ithome.com.tw/upload/images/20190919/20106580cS5SUT1FCA.jpg

我在全域環境底下想要透過 console 取用 hello 函式裡面的變數,卻得到了「 localText is not defined. 」的錯誤,這是因為在取用該變數的當下,hello 函式並沒有被呼叫而產生執行環境,所以對全域來講這個變數是不存在的( 沒有被宣告 )。這就是 Scope 的基本概念。

Function Scope

Function Scope 可以從字面上直接理解為「 在 function 內的 Scope 」,或是「由 function 來決定 Scope 」。延續上一個例子,我在全域環境底下沒辦法取用函式內的變數,是因為該函式的執行環境還沒有被產生,所以我們只要想辦法讓它產生就行了。但要記得離開執行環境,也就是「結束這個函式之後」,在裡面所宣告的變數就無法被取得了,所以只能在函式內被取用。

https://ithelp.ithome.com.tw/upload/images/20190919/20106580tO8quLoxIp.jpg

那如果是反過來,在函式內想要拿到函式外面宣告的變數的話,會不會有問題? 答案是可以的,因為只要還沒有結束整個 JS 主程式,全域環境都會一直存在,而 JS 在找不到變數時,預設的行為就是會向外尋找有沒有相同名字的變數。因此不會有像前一個例子一樣找不到變數宣告的情況產生。

Block Scope

前面說到 Function Scope 是由函式來決定範疇,這是之前 ES5 版本的行為,在 ES6 之後,出現了 let 、 const 等兩種宣告變數的關鍵字,讓我們除了使用 funciton 來定義範疇,也能 「區塊 ( Block )」來定義。什麼是區塊呢?區塊就是兩個大括號裡面的範圍({ 、 }), if 判斷式 、while 或是 for 迴圈語法用到的大括號範圍就是 Block ,當然, function 的大括號也算。

所以什麼是 Block Scope 呢?其實有個 JS 之前就存在的語法 「 try / catch 」 就有 Block Scope 的特性,讓我們先來看看一段程式碼:

https://ithelp.ithome.com.tw/upload/images/20190919/20106580vsMINFQyhh.jpg

由上面這段程式碼來看,在全域的部分,還是可以取得 foo 變數,因為對使用 var 宣告的變數來說,有沒有大括號並不影響範疇,再來讓我們仔細看看 catch 這個特殊語法區塊,他不是函式,但是卻有一個系統提供的變數 err ,這個變數只能在 catch 區塊裡面被使用(通常是拿來丟出錯誤),當我們從全域呼叫時,就會產生錯誤,像這樣的行為就是 Block Scope 的特性。簡言之:

Block Scope 就是用大括號去定義範疇。

總結

這個章節我們了解什麼是範疇、Function Scope 以及 Block Scope 的觀念了,接下來讓我用一個簡單的例子,整合 var 、 let 、const 等宣告方式,看看兩種定義 Scope 的方式會造成變數存取上有什麼不同的結果 :

https://ithelp.ithome.com.tw/upload/images/20190919/20106580zBlWz671lp.jpg

我們在全域的環境下呼叫 bar 函式,照理說在該函式內產生的變數都可以被存取到,但因為 Block Scope 的關係,由 let 與 const 宣告的變數只能在 if 判斷式內被取得。 因為使用 block scope 能夠更靈活地去管理所宣告的變數,避免重複宣告導致的混淆,在 ES6 出現之後, let 與 const 廣為被推薦使用。


上一篇
JS 原力覺醒 Day03 - 執行環境與執行堆疊
下一篇
JS 原力覺醒 Day05 - Scope Chain
系列文
JavaScript 原力覺醒 - 成為絕地武士之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言